--[[
willSendRequestToBbsCgi 関数
  5ch/bbspinkのbbs.cgiにPOSTリクエストが送られる直前にproxy2chから呼び出される
  関数です。引数requestと同じ構造のtable型の変数を返すことで、proxy2chがbbs.cgi
  に対して送るリクエストヘッダ/ボディを自由に改変 (ヘッダの追加/削除含む) する
  ことができます。--bbscgi-headerオプションとは異なり、板ごとに動作を変更する
  ことも可能です。スクリプトは毎回読み込まれるため、proxy2chを起動し直さなくて
  も変更は反映されます。
  * 引数 request
    次のような構造を持つtable型の変数です。送信される予定のリクエストヘッダ/
    ボディが格納されています。
    {
      "body" = "リクエストボディ",
      "headers" = {
        "リクエストヘッダ1" = "値1",
        "リクエストヘッダ2" = "値2",
          :
      }
    }
    この引数をそのまま返せば「何もしない」関数になります。
    request.headersは純粋なtableではなく、tableライクなuserdataです。[]を使った
    値のゲット/セットやpairsを使ったキー/値のイテレーションは可能であるため、
    文字列をキーとした連想配列としてアクセスする限りtable型でないことを意識する
    必要はありません。キー名の大文字小文字を区別しない点がtableとは異なります。
  * 引数 host
    リクエスト先のホスト名が格納されているstring型の変数です。
  * 引数 board
    リクエストボディのbbsフィールドの値に相当するstring型の変数です。
  * 引数 thread
    リクエストボディのkeyフィールドの値に相当するstring型の変数です。
  * 戻り値
    引数requestと同じ構造を持つtable型の値を返してください。requestそのものを
    改変してもいいですし、新しいtableを1から作ってもいいです。
    リクエストボディを改変する場合、application/x-www-form-urlencodedの形式を
    満たしている必要があります。
    リクエストヘッダを改変する際、特定のリクエストヘッダを送信したくない場合は
    値をnilに設定して削除するか、あるいは空の文字列""を代入します。
    引数のrequest.headersはtableではないと書きましたが、ここで返すheadersの型は
    純粋なtableでも良いです。request.headersと同じ型のuserdataを1から作りたい
    場合はHttpHeaders.new()という関数が使えます。

このスクリプトの中からはproxy2chというグローバル変数を参照可能であり、いくつかの
ユーティリティ関数や変数が proxy2ch.(関数名/変数名) の形でアクセスできます。

proxy2ch.hmacSHA256(key, message) 関数
  HMAC-SHA256に基づいてメッセージダイジェストを算出します。
  * 引数 key
    HMAC算出用の鍵をstring型で渡します。
  * 引数 message
    ダイジェストを算出したいメッセージをstring型で渡します。
  * 戻り値
    算出したメッセージダイジェストの値をstring型で返します。
    hex表記したもの、つまり0-9,a-fで構成される長さ64の文字列になります。

proxy2ch.decodeURIComponent(input [, decodePlus]) 関数
  URLエンコードされた文字列をデコードします。
  * 引数 input
    デコードしたい文字列をstring型で渡します。
  * 引数 decodePlus (省略可能)
    input中に現れる"+"を" "に置き換えるかどうかをboolean型で渡します。
    application/x-www-form-urlencoded形式を想定しているため、この引数を省略した
    場合はtrueが渡されたものとして動作します。
  * 戻り値
    デコード済み文字列をstring型で返します。

proxy2ch.encodeURIComponent(input [, spaceAsPlus]) 関数
  文字列をURLエンコードします。
  * 引数 input
    エンコードしたい文字列をstring型で渡します。
  * 引数 spaceAsPlus (省略可能)
    input中に現れる" "を"%20"ではなく"+"に置き換えるかどうかをboolean型で渡しま
    す。application/x-www-form-urlencoded形式を想定しているため、この引数を省略
    した場合はtrueが渡されたものとして動作します。
  * 戻り値
    エンコード済み文字列をstring型で返します。

proxy2ch.convertShiftJISToUTF8(input) 関数
  Shift JISでエンコードされた文字列をUTF-8に変換します。
  * 引数 input
    UTF-8に変換したい文字列をstring型で渡します。
  * 戻り値
    UTF-8に変換された文字列をstring型で返します。
    変換に失敗した場合はnilを返します。

proxy2ch.isKeyExpired(key) 関数
  指定したMonaKeyが有効期限切れ扱いされたことがあるかどうかをチェックします。
  通常は使う必要はないですが、MonaKeyの管理をproxy2ch.monaKeyに丸投げせずに
  スクリプト側で行いたい場合に役に立つ可能性があります。
  * 引数 key
    チェックしたいMonaKeyをstring型で渡します。
  * 戻り値
    有効期限切れ扱いされたことがあるかどうかをboolean型で返します。

proxy2ch.isValidAsUTF8(input) 関数
  指定した文字列がUTF-8エンコーディングで解釈可能かどうかを判定します。
  * 引数 input
    判定したい文字列をstring型で渡します。
  * 戻り値
    UTF-8エンコーディングで解釈可能かどうかをboolean型で返します。

proxy2ch.getMonaKey(userAgent) 関数
  指定したUser-Agentに対して払い出されたMonaKey(仮称)の値を取得します。
  * 引数 userAgent
    MonaKeyを取得したいUser-Agentをstring型で渡します。
  * 戻り値
    指定したUser-Agentに対して払い出されたMonaKeyの値をstring型で返します。
    まだ払い出しが行われていない場合は "00000000-0000-0000-0000-000000000000" を
    返します。

proxy2ch.monaKey string型変数
  書き込み時に払い出されたMonaKey(仮称)の値を格納している変数です。
  払い出し前の初期値は便宜上 "00000000-0000-0000-0000-000000000000" です。

proxy2ch.port 数値型変数
  proxy2chがクライアントの接続を待ち受けているポート番号を格納している変数です。
  複数のポートで待ち受けている、あるいは複数のproxy2chを同時に動かしている時に
  1つのスクリプトでポートによって動作を変えたい時に参照できます。
]]

function createTableFromBody(str)
    local t = {}
    for s in string.gmatch(str, "([^&]+)") do
        local idx = string.find(s, "=")
        local name = string.sub(s, 1, idx-1)
        local value = string.sub(s, idx+1)
        t[name] = value
    end
    return t
end

function createBodyFromTableWithOrder(tBody, order)
    order = order or {}
    local ret = ""
    for i, name in ipairs(order) do
        if tBody[name] ~= nil then
            if ret ~= "" then
                ret = ret .. "&"
            end
            ret = ret .. name .. "=" .. tBody[name]
            tBody[name] = nil
        end
    end
    for name, value in pairs(tBody) do
        if ret ~= "" then
            ret = ret .. "&"
        end
        ret = ret .. name .. "=" .. value
    end
    return ret
end

function generatePostSignatureFromTable(tBody, userAgent, hmacKey)
    local bbs = tBody["bbs"] or ""
    local key = tBody["key"] or ""
    local time = tBody["time"] or ""
    local from = tBody["FROM"] or ""
    local mail = tBody["mail"] or ""
    local message = tBody["MESSAGE"] or ""
    local subject = tBody["subject"] or ""
    local userAgent = userAgent or ""
    local monaKey = proxy2ch.getMonaKey(userAgent)
    local unused = ""
    local nonce = string.format("%d.%03d", os.time(), math.random(0, 999))
    local data = table.concat({bbs, key, time, from, mail, message, subject, userAgent, monaKey, unused, nonce}, "<>")
    return proxy2ch.hmacSHA256(hmacKey, data), nonce, monaKey
end

function willSendRequestToBbsCgi(request, host, board, thread)
    --リクエストヘッダのUser-Agentの値を変更する
    request.headers["User-Agent"] = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:88.0) Gecko/20100101 Firefox/88.0"
    --リクエストヘッダのRefererの値を変更する
    request.headers["Referer"] = "https://" .. host .. "/test/read.cgi/" .. board .. "/" .. thread .. "/l50"
    --リクエストボディの末尾に"&oekaki_thread1="を追加する
    request.body = request.body .. "&oekaki_thread1="

    --リクエストボディをtableに変換する
    --"name1=value1&name=value2"形式のstringが{"name1" = "value1", "name2" = "value2"}形式のtableに変換される
    local tBody = createTableFromBody(request.body)
    --既にリクエストボディがUTF-8になっていると思われる場合はUTF-8への変換を無効にする
    --submitフィールドの値がUTF-8で解釈可能かどうかで判定する
    local shouldConvertToUTF8 = not proxy2ch.isValidAsUTF8(proxy2ch.decodeURIComponent(tBody["submit"]))
    --リクエストボディの各フィールドのURLエンコードを解除し、必要ならUTF-8に変換する
    --リクエストボディのUTF-8化が必須かどうかはUser-Agentにより異なる
    for name, value in pairs(tBody) do
        local decoded = proxy2ch.decodeURIComponent(value)
        tBody[name] = shouldConvertToUTF8 and proxy2ch.convertShiftJISToUTF8(decoded) or decoded
    end
    --リクエストボディとUser-Agentからリクエストのsignatureを生成する
    --signatureの生成元となるリクエストボディはURLエンコードを解除しておく必要がある
    local postSig, postNonce, monaKey = generatePostSignatureFromTable(tBody, request.headers["User-Agent"], "this is an HMAC key")
    --リクエストボディの各フィールドを再度URLエンコードする
    for name, value in pairs(tBody) do
        tBody[name] = proxy2ch.encodeURIComponent(value)
    end
    --tableからフィールドの順序を指定して新しいリクエストボディを生成する
    request.body = createBodyFromTableWithOrder(tBody, {"FROM", "mail", "MESSAGE", "bbs", "key", "time", "submit"})
    --Content-TypeヘッダのcharsetにUTF-8を指定しておく
    request.headers["Content-Type"] = "application/x-www-form-urlencoded; charset=UTF-8"
    --hostがbbspink.comでなければ新しい書き込み仕様に必要なヘッダを設定
    if string.sub(host, -#".bbspink.com") ~= ".bbspink.com" then
        request.headers["X-PostSig"] = postSig
        request.headers["X-APIKey"] = "this is an API key"
        request.headers["X-PostNonce"] = postNonce
        request.headers["X-MonaKey"] = monaKey
    end
    return request
end
